/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#ifdef _DEBUG
#include <stdio.h>
#endif

#include <GPUC/Context.h>
#include "CommonPrivate.h"

/*------------------------------------------------------------------------------------------------*/

#if defined(_WIN32)

LRESULT CALLBACK GPUC_wglWindowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

#else

static int GPUC_glxErrorHandler(Display *display, XErrorEvent *error);

#endif

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_createContext()
{
#if defined(_WIN32)
    return GPUC_createWglContext();
#else
    return GPUC_createGlxContext(0);
#endif
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_destroyContext()
{
#if defined(_WIN32)
    return GPUC_destroyWglContext();
#else
    return GPUC_destroyGlxContext();
#endif
}

/*------------------------------------------------------------------------------------------------*/

#if defined(_WIN32)

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_createWglContext()
{
    const PIXELFORMATDESCRIPTOR pixelFormat =
    {
        sizeof(PIXELFORMATDESCRIPTOR),              // size of this structure
        1,                                          // version
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL,    // flags
        PFD_TYPE_RGBA,                              // pixel format type
        32,                                         // color bits
        0, 0, 0, 0, 0, 0,
        8,                                          // alpha bits
        0,
        0,                                          // accum bits
        0, 0, 0, 0,
        0,                                          // depth bits
        0,                                          // stencil bits
        0,                                          // aux buffers
        PFD_MAIN_PLANE,                             // layer type
        0, 0, 0, 0
    };

    int        pixelFormatIndex;
    GPUC_Error error;

    if (GPUC_contextInitialized)
        return GPUC_Error_InvalidOperation;

    // Get the module handle
    GPUC_wglModuleHandle = GetModuleHandle(NULL);

    if (GPUC_wglModuleHandle == NULL)
        return GPUC_Error_Unknown;

    // Register the window class (1)
    ZeroMemory(&GPUC_wglWindowClass, sizeof GPUC_wglWindowClass);

    GPUC_wglWindowClass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    GPUC_wglWindowClass.lpfnWndProc   = GPUC_wglWindowProcedure;
    GPUC_wglWindowClass.hInstance     = GPUC_wglModuleHandle;
    GPUC_wglWindowClass.lpszClassName = GPUC_wglWindowClassName;

    if (RegisterClass(&GPUC_wglWindowClass) == 0)
        return GPUC_Error_Unknown;

    // Create the window (2)
    GPUC_wglWindowHandle = CreateWindow(GPUC_wglWindowClassName,
                                      NULL,
                                      WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                                      0, 0,
                                      0, 0,
                                      NULL,
                                      NULL,
                                      GPUC_wglModuleHandle,
                                      NULL);

    if (GPUC_wglWindowHandle == NULL)
    {
        error = GPUC_Error_Unknown;
        goto cleanup1;
    }

    // Get the device context (3)
    GPUC_wglDeviceContext = GetDC(GPUC_wglWindowHandle);

    if (GPUC_wglDeviceContext == NULL)
    {
        error = GPUC_Error_Unknown;
        goto cleanup2;
    }

    // Choose and set the pixel format
    pixelFormatIndex = ChoosePixelFormat(GPUC_wglDeviceContext, &pixelFormat);

    if (pixelFormatIndex == 0)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup3;
    }

    if (SetPixelFormat(GPUC_wglDeviceContext, pixelFormatIndex, &pixelFormat) == FALSE)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup3;
    }

    // Create the rendering context (4)
    GPUC_wglRenderingContext = wglCreateContext(GPUC_wglDeviceContext);

    if (GPUC_wglRenderingContext == NULL)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup3;
    }

    // Make the rendering context current (5)
    if (wglMakeCurrent(GPUC_wglDeviceContext, GPUC_wglRenderingContext) == FALSE)
    {
        error = GPUC_Error_Unknown;
        goto cleanup4;
    }

    // Initalize GLEW
    if (glewInit() != GLEW_OK)
    {
        error = GPUC_Error_Unknown;
        goto cleanup5;
    }

    GPUC_contextInitialized = 1;
    return GPUC_Error_None;

    // Cleanup
cleanup5:
    wglMakeCurrent(NULL, NULL);

cleanup4:
    wglDeleteContext(GPUC_wglRenderingContext);

cleanup3:
    ReleaseDC(GPUC_wglWindowHandle, GPUC_wglDeviceContext);

cleanup2:
    DestroyWindow(GPUC_wglWindowHandle);

cleanup1:
    UnregisterClass(GPUC_wglWindowClassName, GPUC_wglModuleHandle);

    return error;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_destroyWglContext()
{
    if (!GPUC_contextInitialized)
        return GPUC_Error_InvalidOperation;

    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(GPUC_wglRenderingContext);
    ReleaseDC(GPUC_wglWindowHandle, GPUC_wglDeviceContext);
    DestroyWindow(GPUC_wglWindowHandle);
    UnregisterClass(GPUC_wglWindowClassName, GPUC_wglModuleHandle);

    GPUC_contextInitialized = 0;
    return GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

LRESULT CALLBACK GPUC_wglWindowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*------------------------------------------------------------------------------------------------*/

#else

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_createGlxContext(const char *displayName)
{   
    const int fbConfigAttribs[] =
    {
        GLX_BUFFER_SIZE,   32,
        GLX_RED_SIZE,      8,
        GLX_GREEN_SIZE,    8,
        GLX_BLUE_SIZE,     8,
        GLX_ALPHA_SIZE,    8,
        GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
        None
    };
    
    int          majorVersion;
    int          minorVersion;
    GLXFBConfig *fbConfigs;
    int          fbConfigCount;
    GPUC_Error   error;
    
    if (GPUC_contextInitialized)
        return GPUC_Error_InvalidOperation;
    
    // Set the error handler (1)
    XSetErrorHandler(GPUC_glxErrorHandler);
    
    // Open the specified display (2)
    GPUC_glxDisplay = XOpenDisplay(displayName);
    
    if (GPUC_glxDisplay == NULL)
        return GPUC_Error_Unknown;
    
    // Check the glX version
    if (glXQueryVersion(GPUC_glxDisplay, &majorVersion, &minorVersion) == False)
    {
        error = GPUC_Error_Unknown;
        goto cleanup2;
    }
    
    if ((majorVersion == 1) && (minorVersion < 3))
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup1;
    }
    
    // Choose a valid framebuffer configuration (3)
    fbConfigs = glXChooseFBConfig(GPUC_glxDisplay, DefaultScreen(GPUC_glxDisplay),
                                  fbConfigAttribs, &fbConfigCount);
    
    if (fbConfigs == NULL)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup2;
    }
    
    // Create the Pbuffer (4)
    GPUC_glxPbuffer = glXCreatePbuffer(GPUC_glxDisplay, *fbConfigs, NULL);
    
    if (GPUC_glxPbuffer == None)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup3;
    }
    
    // Create the GL context (5)
    GPUC_glxContext = glXCreateNewContext(GPUC_glxDisplay, *fbConfigs, GLX_RGBA_TYPE, NULL, True);
    
    if (GPUC_glxContext == NULL)
    {
        error = GPUC_Error_UnsupportedDevice;
        goto cleanup4;
    }
    
    // Make the context current (6)
    if (glXMakeCurrent(GPUC_glxDisplay, GPUC_glxPbuffer, GPUC_glxContext) == False)
    {
        error = GPUC_Error_Unknown;
        goto cleanup5;
    }
    
    // Initalize GLEW
    if (glewInit() != GLEW_OK)
    {
        error = GPUC_Error_Unknown;
        goto cleanup6;
    }
    
    XFree(fbConfigs);
    GPUC_contextInitialized = 1;
    return GPUC_Error_None;
    
    // Cleanup
cleanup6:
    glXMakeCurrent(GPUC_glxDisplay, None, NULL);
    
cleanup5:
    glXDestroyContext(GPUC_glxDisplay, GPUC_glxContext);
    
cleanup4:
    glXDestroyPbuffer(GPUC_glxDisplay, GPUC_glxPbuffer);
    
cleanup3:
    XFree(fbConfigs);
    
cleanup2:
    XCloseDisplay(GPUC_glxDisplay);
    
cleanup1:
    XSetErrorHandler(NULL);
    
    return error;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Error
GPUC_destroyGlxContext()
{
    if (!GPUC_contextInitialized)
        return GPUC_Error_InvalidOperation;
        
    glXMakeCurrent(GPUC_glxDisplay, None, NULL);
    glXDestroyContext(GPUC_glxDisplay, GPUC_glxContext);
    glXDestroyPbuffer(GPUC_glxDisplay, GPUC_glxPbuffer);
    XCloseDisplay(GPUC_glxDisplay);
    XSetErrorHandler(NULL);
    
    GPUC_contextInitialized = 0;
    return GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

int GPUC_glxErrorHandler(Display *display, XErrorEvent *error)
{
#ifdef _DEBUG
    // Print error message
#endif
    
    return 0;
}

/*------------------------------------------------------------------------------------------------*/

#endif

/*------------------------------------------------------------------------------------------------*/
